/**
 * KopsoxFramework
 * Copyright (C) 2010 Free Software Foundation, Inc. <http://fsf.org/>
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.kopsox.data.impl;

import java.io.Serializable;

import org.kopsox.comm.message.ErrorMessage;
import org.kopsox.comm.message.impl.ErrorMessageImpl;
import org.kopsox.comm.message.impl.LoggingMessageImpl;
import org.kopsox.comm.message.impl.ValueMessageImpl;
import org.kopsox.data.Value;
import org.kopsox.data.ValueDescriptor;
import org.kopsox.environment.proxy.KopsoxRegistry;
import org.kopsox.general.priority.LoggingPriorityEnum;
import org.kopsox.general.priority.PriorityEnum;
import org.kopsox.general.type.ErrorTypeEnum;
import org.kopsox.general.type.ValueType;
import org.kopsox.general.type.ValueTypeEnum;
import org.kopsox.util.log.KopsoxLogger;

/**
 * Standardimplementation for the AbstractPolymorph<br>
 * 
 * @author Konrad Renner - 14.08.2010
 * @param <T> 
 *
 */
public class Polymorph<T extends Serializable> extends AbstractPolymorph<T>{
	
	protected final static KopsoxLogger LOGGER  = KopsoxRegistry.INSTANCE.getKopsoxLoggerForClass(Polymorph.class);
	
    private static final long serialVersionUID = 2632218813431772085L;

    private final ValueType typeOfValue;
    
    //Caches
    private volatile int hashCode = 0;
    private volatile String stringRepr = null;
    
    /**
     * Creates an Polymorph with 'noName'
     * 
     * @param value
     */
    public Polymorph(T value){
         this(value,new ValueDescriptorImpl.ValueDescriptorBuilder().build());
    }
    
    /**
     * This constructor loads the Descriptor, Validators, Observers, ... from the ValueEnvironment
     * @param value
     * @param name
     */
    public Polymorph(T value,String name){
         super(value,name,null);
         this.typeOfValue = ValueTypeEnum.getTypeFromValue(getValue());
         super.setValueBackup(value);
    }
    
    /**
     * This constructor loads the Descriptor, Validators, Observers, ... from the ValueEnvironment
     * @param value
     * @param name
     * @param namespace 
     */
    public Polymorph(T value,String name,String namespace){
         super(value,name,namespace);
         this.typeOfValue = ValueTypeEnum.getTypeFromValue(getValue());
         super.setValueBackup(value);
    }
    
    public Polymorph(T value,ValueDescriptor desc){
         super(value,desc);
         this.typeOfValue = ValueTypeEnum.getTypeFromValue(getValue());
         super.setValueBackup(value);
    }
    
    @SuppressWarnings("unchecked")
    public Polymorph<T> changeValueWithString(final String value){
        Object converted = getType().convert(value);
        if(converted == null) {
                ErrorMessage message = new ErrorMessageImpl(PriorityEnum.HIGHEST.getPriority(), ErrorTypeEnum.FALSETYPE);
                getValidationErrors().add(message);
                
                return this;
        }
        return changeValue((T)converted);
    }

    @SuppressWarnings("unchecked")
    @Override
    public Polymorph<T> changeValue(final T value) {
    	long time = 0;
    	
    	if(LOGGER.isLogEnabeld(LoggingPriorityEnum.ALL.getPriority())) {
    		time = LOGGER.startLog("changeValue", LoggingPriorityEnum.ALL.getPriority());
    		LOGGER.writeLogMessage(new LoggingMessageImpl("OldValue:"+getValue()+";NewValue:"+value+";","changeValue",LoggingPriorityEnum.ALL.getPriority()));
    	}
        
        //it should just be changed if its not the same
        if(getValue() == null && value == null){
                return this;
        }else if(getValue() != null && getValue().equals(value) ) {
                return this;
        }
        
        boolean everthingOk = true;
        
        //Create the new value
        Polymorph<T> newValue = new Polymorph<T>(value,this.getValueDescriptor());
        newValue.addAllObservers(this.getAllObservers());
        newValue.addAllValidators(this.getAllValidators());
        
        newValue.setIsDirty(true);
        
        //The Backup should hold the value from the creation time
        newValue.setValueBackup(getValueBackup());
        
        //First lets check the validators, if all is correct
        ValueMessageImpl message = new ValueMessageImpl((Value<Serializable>) newValue);
        message.setMessageSender(this);
        message.setOldValue(getValue());
        
        if(newValue.getValueDescriptor().isAutoValidationEnabled()){
                everthingOk = newValue.notifyValidators(message);
        }
        
        if(everthingOk){
                
                newValue.notifyObservers(message);
                
                //If everything is ok, old ValidationErrors are obsolet
                super.getValidationErrors().clear();
                
                if(LOGGER.isLogEnabeld(LoggingPriorityEnum.ALL.getPriority())) {
            		LOGGER.endLog("changeValue", LoggingPriorityEnum.ALL.getPriority(), time);
            	}
                
                return newValue;
        }
        
        //The validation errors have to be mapped in this object
        getValidationErrors().addAll(newValue.getValidationErrors());
        
        if(LOGGER.isLogEnabeld(LoggingPriorityEnum.ALL.getPriority())) {
    		LOGGER.endLog("changeValue", LoggingPriorityEnum.ALL.getPriority(), time);
    	}
        
        return this;
    }

    @SuppressWarnings("unchecked")
    @Override
    public boolean equals(Object obj) {
    	if(obj == null){
    		return false;
    	}
    	
        if (this == obj)
                return true;
        if (getClass() != obj.getClass())
                return false;
        Polymorph other = (Polymorph) obj;
        
        if (typeOfValue == null) {
                if (other.typeOfValue != null)
                        return false;
        } else if (!typeOfValue.equals(other.typeOfValue))
                return false;
        
        if (getValue() == null) {
                if (other.getValue() != null)
                        return false;
        } else if (!getValue().equals(other.getValue()))
                return false;
        if (getValueDescriptor() == null) {
                if (other.getValueDescriptor() != null)
                        return false;
        } else if (!getValueDescriptor().equals(other.getValueDescriptor()))
                return false;
        return true;
    }

    @Override
    public int hashCode() {
        int result = this.hashCode;
        if(result == 0) {
                final int prime = 31;
                result = 17;
                result = prime * result + ((typeOfValue == null) ? 0 : typeOfValue.hashCode());
                result = prime * result + ((getValue() == null) ? 0 : getValue().hashCode());
                result = prime * result + ((getValueDescriptor() == null) ? 0 : getValueDescriptor().hashCode());
                this.hashCode = result;
        }
        return result;
    }
    
    @Override
    public String toString(){
        if(this.stringRepr == null) {
            StringBuilder sb = new StringBuilder();
            sb.append("Polymorph[Value:");
            sb.append(getValue());
            sb.append(",BackupValue:");
            sb.append(getValueBackup());
            sb.append(",Type:");
            sb.append(getType());
            sb.append(",isDirty:");
            sb.append(isDirty());
            sb.append(",ValueDescriptor:");
            sb.append(getValueDescriptor());
            sb.append(",ValidationErrors:");
            sb.append(getValidationErrors());
            sb.append(",Validators:");
            sb.append(getAllValidators());
            sb.append(",Observers:");
            sb.append(getAllObservers());
            sb.append("]");
            
            this.stringRepr = sb.toString();
        }
        
        return stringRepr;
    }

    @Override
    public ValueType getType() {
        return this.typeOfValue;
    }
}
